
#include <BMap>
#include <BRender>

#include "LineStripe.h"

LineStripe::LineStripe()
{
	_thickness = 0;
	_headSphere = new BSphere();
	_headSphere->setColor(255, 255, 255, 255);
	_tailCone = new BCone();
	_tailCone->setColor(255, 255, 255, 255);

	_length = 0;
	_headLength = 0;
	_tailLength = 0;

	this->setLineWidth(2);
	this->setPointSize(3);
	this->setPointSmooth(true);
	connect(this, Signal_Transformed, &LineStripe::slotTransformed);
}
LineStripe::LineStripe(const BVectorArray& linePoints)
{
	_thickness = 0;
	_length = 0;
	this->setLineWidth(2);
	this->setPointSize(3);
	this->setPointSmooth(true);
	_linePoints = linePoints;
}
LineStripe::~LineStripe()
{

}

void LineStripe::copy(const BShape* other)
{
	BShape::copy(other);
	if (const LineStripe* stripe = dynamic_cast<const LineStripe*>(other))
	{
		_thickness = stripe->_thickness;
		_headSphere->copy(stripe->_headSphere);
		_tailCone->copy(stripe->_tailCone);
		_linePoints = stripe->_linePoints;
		_length = stripe->_length;
	}
}
BShape* LineStripe::clone() const
{
	LineStripe* stripe = new LineStripe();
	stripe->copy(this);
	return stripe;
}

BLine LineStripe::line(BReal value) const
{
	if (_linePoints.size() < 2)
		return BLine();
	if (value < 0)
	{
		const BVector& v0 = _linePoints[0];
		const BVector& v1 = _linePoints[1];
		return BLine(v0, v1);
	}
	for (int i = 1; i < _linePoints.size(); i++)
	{
		const BVector& v0 = _linePoints[i - 1];
		const BVector& v1 = _linePoints[i];
		BReal len = (v0 - v1).length();
		if (value > len)
		{
			value -= len;
			continue;
		}
		return BLine(v0, v1);
	}
	const BVector& v0 = _linePoints.at(_linePoints.size() - 2);
	const BVector& v1 = _linePoints.at(_linePoints.size() - 1);
	return BLine(v0, v1);
}
BVector LineStripe::vertex(BReal value) const
{
	if (_linePoints.size() < 2)
	{
		BVector point = _linePoints.first() * this->matrix();
		return point;
	}
	if (value < 0)
	{
		const BVector& v0 = _linePoints[0];
		const BVector& v1 = _linePoints[1];
		BVector point = v0 + (v1 - v0).normal() * value;
		point *= this->matrix();
		return point;
	}
	for (int i = 1; i < _linePoints.size(); i++)
	{
		const BVector& v0 = _linePoints[i - 1];
		const BVector& v1 = _linePoints[i];
		BReal len = (v0 - v1).length();
		if (value > len)
		{
			value -= len;
			continue;
		}
		BVector point = v0 + (v1 - v0).normal() * value;
		point *= this->matrix();
		return point;
	}
	const BVector& v0 = _linePoints.at(_linePoints.size() - 2);
	const BVector& v1 = _linePoints.at(_linePoints.size() - 1);
	BVector point = v1 + (v1 - v0).normal() * value;
	point *= this->matrix();
	return point;
}
BReal LineStripe::value(const BVector& point) const
{
	BReal minDist = REAL_MAX;
	BReal value = 0;
	BReal section_begin = 0;
	for (int i = 1; i < _linePoints.size(); i++)
	{
		BVector v0 = _linePoints[i - 1] * this->matrix();
		BVector v1 = _linePoints[i] * this->matrix();
		BLine line(v0, v1);
		BVector vertex = line.closest(point, false);
		BReal dist2 = (vertex - point).length2();
		BReal length = (vertex - v0).length();
		if (dist2 < minDist)
		{
			minDist = dist2;
			value = section_begin + length;
		}
		section_begin += line.length();
	}
	return value;
}

BReal LineStripe::length() const
{
	this->flush();
	return _length;
}
BReal LineStripe::headLength() const
{
	this->flush();
	return _headLength;
}
BReal LineStripe::tailLength() const
{
	this->flush();
	return _tailLength;
}

bool LineStripe::closeLoop() const
{
	if (_linePoints.size() < 2)
		return false;
	BReal len2 = (_linePoints.first() - _linePoints.last()).length2();
	if (len2 > REAL_EPSILON)
		return false;
	return true;
}
bool LineStripe::reverse()
{
	if (_linePoints.size())
	{
		_linePoints.reverse();
		this->dirty();
		return true;
	}
	return false;
}

void LineStripe::setLinePoints(const BVectorArray& linePoints)
{
	_linePoints = linePoints;
	this->dirty();
}
const BVectorArray& LineStripe::linePoints() const
{
	return _linePoints;
}

int LineStripe::pointCount() const
{
	return _linePoints.size();
}

bool LineStripe::setPoint(int index, const BVector& point)
{
	return _linePoints.set(index, point);
}
const BVector& LineStripe::point(int index) const
{
	return _linePoints.at(index);
}

void LineStripe::setThickness(BReal thickness)
{
	if (_thickness != thickness)
	{
		_thickness = thickness;
		this->dirty();
	}
}
BReal LineStripe::thickness() const
{
	return _thickness;
}

bool LineStripe::appendPoint(const BVector& point)
{
	if (_linePoints.append(point))
	{
		this->dirty();
		return true;
	}
	return false;
}
bool LineStripe::removePoint(int index)
{
	if (_linePoints.remove(index))
	{
		this->dirty();
		return true;
	}
	return false;
}

const BVector& LineStripe::head() const
{
	return _linePoints.first();
}
const BVector& LineStripe::tail() const
{
	return _linePoints.last();
}

void LineStripe::setHeadColor(const BColor& headColor)
{
	if (_headSphere->color() != headColor)
	{
		_headSphere->setColor(headColor);
		this->fresh();
	}
}
const BColor& LineStripe::headColor() const
{
	return _headSphere->color();
}

void LineStripe::setHeadVisible(bool visible)
{
	_headSphere->setVisible(visible);
	this->fresh();
}
bool LineStripe::headVisible() const
{
	return _headSphere->visible();
}

void LineStripe::setTailColor(const BColor& tailColor)
{
	if (_tailCone->color() != tailColor)
	{
		_tailCone->setColor(tailColor);
		this->fresh();
	}
}
const BColor& LineStripe::tailColor() const
{
	return _tailCone->color();
}

void LineStripe::setTailVisible(bool visible)
{
	_tailCone->setVisible(visible);
	this->fresh();
}
bool LineStripe::tailVisible() const
{
	return _tailCone->visible();
}

bool LineStripe::extendHead(LineStripe* track)
{
	if (_headTrack != track)
	{
		_headTrack = track;
		if (_headTrack)
		{
			BVector head = _headTrack->head() * _headTrack->matrix();
			BVector tail = _headTrack->tail() * _headTrack->matrix();
			BVector first = _linePoints.first() * this->matrix();
			BReal dist_head = (first - head).length();
			BReal dist_tail = (first - tail).length();
			if (dist_head < dist_tail)
			{
				_headTrack->reverse();
			}
		}
		this->dirty();
		this->fresh();
		return true;
	}
	return false;
}
bool LineStripe::extendTail(LineStripe* track)
{
	if (_tailTrack != track)
	{
		_tailTrack = track;
		if (_tailTrack)
		{
			BVector head = _tailTrack->head() * _tailTrack->matrix();
			BVector tail = _tailTrack->tail() * _tailTrack->matrix();
			BVector last = _linePoints.last() * this->matrix();
			BReal dist_head = (last - head).length();
			BReal dist_tail = (last - tail).length();
			if (dist_head > dist_tail)
			{
				_tailTrack->reverse();
			}
		}
		this->dirty();
		this->fresh();
		return true;
	}
	return false;
}

LineStripe* LineStripe::headTrack() const
{
	return _headTrack;
}
LineStripe* LineStripe::tailTrack() const
{
	return _tailTrack;
}

bool LineStripe::query(const BString& text, BValue& value) const
{
	return BShape::query(text, value);
}

void LineStripe::slotTransformed(const BObject* object, const BValue& value)
{

}

void LineStripe::refresh(BBox& box)
{
	_length = 0;

	if (_linePoints.size())
	{
		_headSphere->setPosition(_linePoints.first());
		_headSphere->setRadius(_thickness);

		BVector tail = _linePoints.last();
		BVector direct = tail - _linePoints[_linePoints.size() - 2];
		direct.normalize();
		_tailCone->setRadius(_thickness);
		_tailCone->setHeight(_thickness * 3);
		_tailCone->setPosition(tail - direct * _tailCone->height());
		_tailCone->setRotate(BQuater(BVector(0, 0, 1), direct));
	}

	BSpace space = _headSphere->space();
	for (int i = 1; i < _linePoints.size(); i++)
	{
		const BVector& v0 = _linePoints[i - 1];
		const BVector& v1 = _linePoints.at(i);
		space.expand(v1);
		_length += (v0 - v1).length();
	}
	space.expand(_tailCone->space());

	_headLength = 0;
	if (_headTrack)
	{
		const BVectorArray& points = _headTrack->linePoints();
		for (int i = 1; i < points.size(); i++)
		{
			const BVector& v0 = points[i - 1];
			const BVector& v1 = points.at(i);
			space.expand(v1);
			_headLength += (v0 - v1).length();
		}
	}

	_tailLength = 0;
	if (_tailTrack)
	{
		const BVectorArray& points = _tailTrack->linePoints();
		for (int i = 1; i < points.size(); i++)
		{
			const BVector& v0 = points[i - 1];
			const BVector& v1 = points.at(i);
			space.expand(v1);
			_tailLength += (v0 - v1).length();
		}
	}

	box = space;
}
void LineStripe::render(BRender& render)
{
	render.drawLineStrip(_linePoints);
	bool selected = this->property("selected");
	int count = _linePoints.size();
	if (selected && count > 1)
	{
		render.setDepthTest(false);
		render.drawShape(_headSphere);
		render.drawShape(_tailCone);
	}
}